redis数据结构 字典 哈希表 用于hash键底层实现 总结

您所在的位置:网站首页 redis hash 底层实现 redis数据结构 字典 哈希表 用于hash键底层实现 总结

redis数据结构 字典 哈希表 用于hash键底层实现 总结

2024-07-11 10:06| 来源: 网络整理| 查看: 265

1.原理 字典是哈希键的底层实现,当一个哈希键包含的键值对比较多,或者键值对中元素都是比较长的字符串,Reids就会使用字典作为哈希键的底层实现

2.字典节点结构

typedef struct dictEntry { void *key //键 union { void *val; unit64_t u64; int64_t s64; }v;//值 struct dictEntry *next; // 指向下一个哈希表节点 形成链表 }dictEntry;

字典节点也是链表结构,用于哈希冲突后形成链表

3.字典结构

typedef struct dict { dictType *type //类型特定函数 void *privdata是// 私有数据 dictht ht[2]; //哈希表 int trehashidx; // rehash索引 当rehash不在进行时 值位-1 }dict;

type属性和privdata属性针对不同类型的键值对,为创建多态字典而设置的 dictType结构里面封装了各自方法 ht[2]是一个包含两个哈希表的数组,一般情况,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时候使用,即在对字典扩容的时候使用

4.dictType结构

typedef struct dictType { unsigned int (*hashFunction) (const void *key); //计算哈希值的函数 void *(*keyDup) (void *privdata, const void *key); //复制键的函数 void *(*valDup) (void *privdata, const void *obj); //复制值的函数 int (*keyCompare) (void *privdata, const void *key1, const void *key2); //对比键的函数 void (*keyDestructor) (void *privdata, void *key) //销毁键的函数 void (*valDeestructor) (void *privdata, void *bje) //销毁值的函数 }dictType;

5.哈希算法 添加一个新的键值对到字典里面时,根据键值对计算出哈希值和索引值,然后根据索引值,把包含新键值对的哈希表节点放到哈希表数组指定索引上

hash = dict->type->hashFunction(key) 使用字典设置的哈希函数,计算键key的哈希值,然后使用哈希表的sizemask属性和哈希值,计算出索引值 index = hash & dict->ht[x]->sizemask;

6.解决键冲突 Redis的哈希表使用链地址法来解决键冲突,因为dictEntry节点组成的链表没有指向链表表尾的指针,为了速度考虑,总是将新节点添加到链表的表头位置

7.rehash 随着操作的不断执行,哈希表保存的键值对会逐渐地增多或减少,为了让哈希表的负载因子维持维持在一个合理的范围之内,当哈希表保存的键值对数量太多或太少,程序需要对哈希表的大小进行相应的扩展或收缩

8.扩容和缩容操作 扩展和收缩哈希表,可以通过执行rehash作来完成

为字典的ht[1]哈希表分配空间,大小取决于要执行的操作,以及ht[0]当前包含的键值对数量 扩展操作 设置ht[1]大小为第一个2倍ht[0]已使用空间大的2的次幂 收缩操作 设置ht[1]大小为第一个二分之一ht[0]已使用空间小的2的次幂将保存在ht[0]中是所有键值对rehash到ht[1]上面,rehash指的是重新计算键的哈希值和索引值,将键值对放置到,ht[1]哈希表指定位置上当ht[0]包含的所有键值对都迁移到了ht[1]之后 ht[0]变为空表,释放ht[0] 将ht[1]设置为ht[0],并在ht[1]新创建一个空白hash表,为下一次rehash做准备

8.扩容和缩容操作时机 负载因子 = 哈希表已经保存节点数量 / 哈希表大小

服务器目前没有执行BGSAVE命令或BGREWRITEAOF, 并且负载因子大于1服务器目前正在执行BGSAVE命令或者BGREWRITEAOF,并且负载因子大于5当负载因子小于0.1时程序自动开始对哈希表执行收缩操作

9.渐进式rehash 扩展或收缩哈希表需要将ht[0]里面的所有键值对rehash到ht[1],但是这个rehash动作并不是一次、集中式地完成的,而是分多次、渐进式完成的

10.渐进式rehash步骤

为ht[1]分配空间在字典中维持一个索引计数器变量 rehashidx 并将它的值设置为0,表示rehash工作正式开始进行rehash期间,每次对字典执行添加、删除、查找或者更新操作,程序除了执行指定的操作,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1]中,当rehash工作完成后,程序将rehashidx属性的值增一,随着字典操作不断执行,最终在某个时间点 ht[0]的所有键值对都被rehash至ht[1],这时程序将rehashidx属性设置为-1,表示rehash操作已经完成,渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到字典的每个添加、删除、查找和更新,操作上,从而避免了集中式rehash而带来的庞大计算量

11.渐进式rehash执行期间的哈希表操作 在渐进式rehash操作中,字典会同时使用ht[0]和ht[1]两个哈希表,所以在渐进式rehash进行期间,字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行。例如在字典上查找一个键如果在ht[0]没有找到,就去ht[1]找在渐进式rehash执行期间,新添加到字典的键值对一律会保存到ht[1]里面而ht[0]不执行任何添加操作,这一措施保证了ht[0]包含的键值对数量会只减少不增加,并随着rehash操作的执行而最终变成了空表

12.总结 1.字典被广泛用于实现Redis的各种功能,包括数据库和哈希表 2.Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,一个仅在进行rehash使用 3.当字典被用作数据库的底层实现,或者哈希键的底层实现,Reids使用 MurmurHash2算法来计算键的哈希值 4.哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向链表 5.在对哈希表进行扩展或收缩操作时,程序需要将现有哈希表包含的所有键值对对rehash到新哈希表里面,并且这个rehash过程并不是一次性地完成的,而是渐进式地完成的

参考 《Redis设计与实现》



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3